#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <regex>
#include <iostream>
#include <sstream>
#include <algorithm>
#include "Base.h"
#include "GraphicsEngine.h"
#include "Files.h"

mat4 gfx::projectionMatrix;
mat4 gfx::viewMatrix;

void gfx::Initialise(){
	
	if (SDL_Init(SDL_INIT_EVERYTHING) < 0){
		Engine::ThrowError("Failed to initialise SDL");
	}

	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
	
	// GL Versions
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
	
	gfxSettings = LoadConfigs(Configs::GetConfig("graphics.cfg"));
	
	int scrnW = gfxSettings.scrnW;
	int scrnH = gfxSettings.scrnH;

	// Double Buffer & Vsync
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, gfxSettings.doubleBuffer);
	SDL_GL_SetSwapInterval(gfxSettings.vsync);

	int flags = 0;

	if (gfxSettings.fullScreen){
		flags = SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN;
	}else{
		flags = SDL_WINDOW_OPENGL;
	}

	// Create SDL Window
	window = SDL_CreateWindow(Engine::programName.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, scrnW, scrnH, flags);
	if (!window){
		Engine::ThrowError("Failed to create viewport");
	}

	// Create OpenGL context and attach it to the window
	context = SDL_GL_CreateContext(window);

	glewExperimental = GL_TRUE;
	glewInit();

	// Move the camera back slightly from the world center by default
	viewMatrix = glm::scale(viewMatrix, glm::vec3(0.2f, 0.2f, 0.2f));
	viewMatrix = glm::translate(viewMatrix, glm::vec3(0.0f, -3.0f, -10.0f));

	// Perspective viewport
	projectionMatrix = glm::perspective(45.0f, (float)scrnW / (float)scrnH, 0.3f, 1000.0f);

	Engine::LogError("Initialised graphics engine", Engine::errorType::TYPE_INFO);

	glEnable(GL_DEPTH_TEST); // depth testing on by default
	glFrontFace(GL_CCW);
	glCullFace(GL_BACK);
	glEnable(GL_CULL_FACE);
}

GFXSettings gfx::LoadConfigs(list<string> cfg){
	list<string> validConfigs;
	list<string> valueName;
	list<string> missingConfigs;
	list<string> outConfigs;
	GFXSettings newSettings;
	
	string valueNames[] = {
		"vsync",
		"doubleBuffer",
		"screenWidth",
		"screenHeight",
		"fullscreen",
		"AALevel",
		"GLVersion"
	};

	int valueCount = sizeof(valueNames) / sizeof(valueNames[0]);
	for (int i = 0; i < valueCount; i++){
		valueName.push_back(valueNames[i]);
	}

	string defaultValues[] = {
		"1",
		"1",
		"640",
		"480",
		"0",
		"0",
		"3"
	};

	if (cfg.empty()){
		return LoadDefaultConfigs();
	}

	// add all valid config settings & values to the valid configs list
	std::list<string>::iterator iterator = cfg.begin();
	while (iterator != cfg.end()) {
		for (int i = 0; i < valueCount; i++){
			regex e("(\\w+[a-z])");
			smatch match;
			if (regex_search(*iterator, match, e)){
				if (match.str().c_str() == valueNames[i]){
					validConfigs.push_back(*iterator);
				}
			}
		}
		iterator++;
	}

	//!!!!!!!!!!!!!!!!!!!!!!!
	// check here for missing configs and set them to defaults
	//!!!!!!!!!!!!!!!!!!!!!!!

	if (validConfigs.size() != valueName.size()){
		printf("There are some missing graphics configs. Setting to default values \n");

		for (int i = 0; i < valueCount; ++i){
			list<string>::iterator iterator = validConfigs.begin();
			while (iterator != validConfigs.end()){
				regex e("(\\w+[a-z])");
				smatch match;
				if (regex_search(*iterator, match, e)){
					string valName = match.str().c_str();
					if (valName == valueNames[i]){
						break;
					}
					if (++iterator == validConfigs.end()){
						missingConfigs.push_back(valueNames[i] + "=" + defaultValues[i]);
						string temp = valueNames[i] + "=" + defaultValues[i];
					}
				}
			}
		}
	}

	iterator = missingConfigs.begin();
	while (iterator != missingConfigs.end()){
		validConfigs.push_back(*iterator);
		++iterator;
	}

	// read all the valid configs and set the graphics settings to their values
	iterator = validConfigs.begin();
	while (iterator != validConfigs.end()) {
		regex e("(\\w+[a-z])");
		smatch match;
		if (regex_search(*iterator, match, e)){
			string valName = match.str().c_str();
			regex valRegex("\\=(.*[0-9])");
			smatch valMatch;
			int value;
			if (!regex_search(*iterator, valMatch, valRegex)){
				for (int i = 0; i < sizeof(valueNames) / sizeof(string); i++){
					if (valueNames[i] == valName){
						istringstream(defaultValues[i]) >> value;
						break;
					}
				}
			}
			else{
				istringstream(valMatch.str().substr(1, valMatch.str().size()).c_str()) >> value;
			}
			if (valName == "vsync"){
				value = std::max(0, std::min(value, 1));
				newSettings.vsync = value;	
			}
			else if (valName == "doubleBuffer"){
				value = std::max(0, std::min(value, 1));
				newSettings.doubleBuffer = value;		
			}
			// Min and max screen resolutions
			else if (valName == "screenWidth"){
				value = std::max(640, std::min(value, 4096));
				newSettings.scrnW = value;
			}
			else if (valName == "screenHeight"){
				value = std::max(480, std::min(value, 2400));
				newSettings.scrnH = value;
			}
			else if (valName == "fullscreen"){
				value = std::max(0, std::min(value, 1));
				newSettings.fullScreen = value;
			}
			else if (valName == "AALevel"){
				if (value != 0 || value != 2 || value != 4 || value != 8){
					if (value > 8){
						value = 8;
					}else{
						value = 0;
					}
				}
				newSettings.AALevel = value;
			}
			else if (valName == "GLVersion"){
				value = std::max(1, std::min(value, 3));
			}
			string val;
			stringstream ov;
			ov << value;
			ov >> val;
			outConfigs.push_back(valName + " = " + val);
		}

		++iterator;
	}

	string nConfig;
	iterator = outConfigs.begin();
	while (iterator != outConfigs.end()){
		nConfig += *iterator + "\n";
		++iterator;
	}

	Configs::SetConfig("graphics.cfg", nConfig);

	Engine::LogError("Sucessfully loaded graphics settings.", Engine::errorType::TYPE_INFO);
	return newSettings;
}

GFXSettings gfx::LoadDefaultConfigs(){
	//create new cfg file with default values
	GFXSettings newSettings;
	newSettings.doubleBuffer = 1;
	newSettings.fullScreen = 0;
	newSettings.vsync = 1;
	newSettings.glLevel = 3;
	newSettings.scrnH = 480;
	newSettings.scrnW = 640;
	newSettings.AALevel = 0;

	string defaultSettings[] = {
		"vsync = 1",
		"doubleBuffer = 1",
		"screenWidth = 640",
		"screenHeight = 480",
		"AALevel = 0",
		"fullscreen = 0",
		"GLVersion = 3"
	};

	string nConfigs;
	for (int i = 0; i < sizeof(defaultSettings) / sizeof(string); i++){
		nConfigs.append(defaultSettings[i] + "\n");
	}

	Configs::ReplaceConfig("graphics.cfg", nConfigs);

	return newSettings;
}

void gfx::DestroyRenderer(){
	SDL_GL_DeleteContext(context);
	SDL_Quit();
}

void gfx::SetBackgroundColour(Colour col){
	bgColour = col;
}

void gfx::SwapBuffers(){
	SDL_GL_SwapWindow(window);
	glClearColor(bgColour.r, bgColour.g, bgColour.b, bgColour.a);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}


Colour::Colour(){ r = 1.0f; g = 1.0f; b = 1.0f; a = 1.0f; }
Colour::Colour(float nR, float nG, float nB, float nA) : r(nR), g(nG), b(nB), a(nA){}
